package com.xxx.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xxx.listener.SystemListener;

/**
 * RSA 加密/解密工具类
 * 
 * 
 * 
 */
public class RSAUtil {
	
	private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
	
	/**
	 * 加密算法RSA
	 */
	public static final String KEY_ALGORITHM = "RSA";

	/**
	 * 签名算法
	 */
	public static final String SIGNATURE_ALGORITHM = "MD5withRSA";

	/**
	 * 获取公钥的key
	 */
	private static final String PUBLIC_KEY = "RSAPublicKey";

	/**
	 * 获取私钥的key
	 */
	private static final String PRIVATE_KEY = "RSAPrivateKey";

	/**
	 * RSA最大加密明文大小
	 */
	private static final int MAX_ENCRYPT_BLOCK = 117;

	/**
	 * RSA最大解密密文大小
	 */
	private static final int MAX_DECRYPT_BLOCK = 128;

	private static char[] HEXCHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			'a', 'b', 'c', 'd', 'e', 'f' };
	
	/**
	 * 公钥和私钥文件名
	 */
	public static String publicKeyPath = "rsa_public_key.pem";
	public static String privateKeyPath = "rsa_private_key.pem";
	
	/**
	 * 获取私钥文件
	 */
	public static File getPrivateFile(){
		String basePath = "";
		if(CommonUtil.isWin()) {
			basePath = RSAUtil.class.getResource("/rsa/").getPath();// 本地debug用
			if(basePath.startsWith("/")) {
				basePath = basePath.substring(1);
			}
		} else if (CommonUtil.isLinux()) {
			basePath = SystemListener.realPath + "/WEB-INF/classes/rsa/";
		}
		String filePath = Escape.unescape(basePath + privateKeyPath);
		return new File(filePath);
	}
	
	/**
	 * 获取公钥文件
	 */
	public static File getPublicFile(){
		String basePath = "";
		if(CommonUtil.isWin()) {
			basePath = RSAUtil.class.getResource("/rsa/").getPath();// 本地debug用
			if(basePath.startsWith("/")) {
				basePath = basePath.substring(1);
			}
		} else if (CommonUtil.isLinux()) {
			basePath = SystemListener.realPath + "/WEB-INF/classes/rsa/";
		}
		String filePath = Escape.unescape(basePath + publicKeyPath);
		return new File(filePath);
	}

	/**
	 * 生成秘钥对
	 * 
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static KeyPair generateKey() throws NoSuchAlgorithmException {
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		keyPairGen.initialize(1024, new SecureRandom());
		KeyPair keyPair = keyPairGen.generateKeyPair();
		return keyPair;
	}

	/**
	 * Encrypt String.
	 * 
	 * @return byte[]
	 */
	public static byte[] encrypt(RSAPublicKey publicKey, byte[] data) {
		if (publicKey != null) {
			try {
				Cipher cipher = Cipher.getInstance(KEY_ALGORITHM,
						new BouncyCastleProvider());
				cipher.init(Cipher.ENCRYPT_MODE, publicKey);
				return cipher.doFinal(data);
			} catch (Exception e) {
				return null;
			}
		}
		return null;
	}

	/**
	 * Basic decrypt method
	 * 
	 * @return byte[]
	 */
	public static byte[] decrypt(RSAPrivateKey privateKey, byte[] raw) {
		if (privateKey != null) {
			try {
				Cipher cipher = Cipher.getInstance(KEY_ALGORITHM,
						new BouncyCastleProvider());
				cipher.init(Cipher.DECRYPT_MODE, privateKey);
				return cipher.doFinal(raw);
			} catch (Exception e) {
				logger.error("decrypt Exception " + e.getMessage(), e);
			}
		}

		return null;
	}

	public static String toHexString(byte[] b) {
		StringBuilder sb = new StringBuilder(b.length * 2);
		for (int i = 0; i < b.length; i++) {
			sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
			sb.append(HEXCHAR[b[i] & 0x0f]);
		}
		return sb.toString();
	}

	public static final byte[] toBytes(String s) {
		byte[] bytes;
		bytes = new byte[s.length() / 2];
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2),
					16);
		}
		return bytes;
	}

	/**
	 * 生成密钥对(公钥和私钥)
	 * 
	 * @return
	 * @throws Exception
	 */
	public static Map<String, Object> genKeyPair() throws Exception {
		KeyPair keyPair = generateKey();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		Map<String, Object> keyMap = new HashMap<String, Object>(2);
		keyMap.put(PUBLIC_KEY, publicKey);
		keyMap.put(PRIVATE_KEY, privateKey);
		return keyMap;
	}

	/**
	 * 用私钥对信息生成数字签名
	 * 
	 * @param data
	 *            已加密数据
	 * @param privateKey
	 *            私钥(BASE64编码)
	 * 
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] data, String privateKey) throws Exception {
		byte[] keyBytes = Base64Utils.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateK);
		signature.update(data);
		return Base64Utils.encode(signature.sign());
	}

	/**
	 * 校验数字签名
	 * 
	 * @param data
	 *            已加密数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @param sign
	 *            数字签名
	 * 
	 * @return
	 * @throws Exception
	 * 
	 */
	public static boolean verify(byte[] data, String publicKey, String sign)
			throws Exception {
		byte[] keyBytes = Base64Utils.decode(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey publicK = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicK);
		signature.update(data);
		return signature.verify(Base64Utils.decode(sign));
	}

	/**
	 * 私钥解密
	 * 
	 * @param encryptedData
	 *            已加密数据
	 * @param privateKey
	 *            私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] encryptedData,
			String privateKey) throws Exception {
		byte[] keyBytes = Base64Utils.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");//keyFactory.getAlgorithm()
		cipher.init(Cipher.DECRYPT_MODE, privateK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段解密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher
						.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher
						.doFinal(encryptedData, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}

	/**
	 * 公钥解密
	 * 
	 * @param encryptedData
	 *            已加密数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPublicKey(byte[] encryptedData,
			String publicKey) throws Exception {
		byte[] keyBytes = Base64Utils.decode(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
		cipher.init(Cipher.DECRYPT_MODE, publicK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段解密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}

	/**
	 * 公钥加密
	 * 
	 * @param data
	 *            源数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data, String publicKey)
			throws Exception {
		byte[] keyBytes = Base64Utils.decode(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		// 对数据加密
		Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");//RSA/NONE/PKCS1Padding,keyFactory.getAlgorithm()解决javax.crypto.BadPaddingException: Blocktype异常的几种解决办法"RSA/ECB/PKCS1Padding"
		cipher.init(Cipher.ENCRYPT_MODE, publicK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段加密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	/**
	 * 私钥加密
	 * 
	 * @param data
	 *            源数据
	 * @param privateKey
	 *            私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
			throws Exception {
		byte[] keyBytes = Base64Utils.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
		cipher.init(Cipher.ENCRYPT_MODE, privateK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段加密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	/**
	 * 获取私钥
	 * 
	 * @param keyMap
	 *            密钥对
	 * @return
	 * @throws Exception
	 */
	public static String getPrivateKey(Map<String, Object> keyMap)
			throws Exception {
		Key key = (Key) keyMap.get(PRIVATE_KEY);
		return Base64Utils.encode(key.getEncoded());
	}
	
	/**
	 * 获取私钥
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static PrivateKey getPrivateKey(String key) throws Exception {
		byte[] keyBytes;   
		keyBytes = Base64Utils.decode(key);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); // 私钥使用PKCS8EncodedKeySpec
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);    
		PrivateKey privateKey = keyFactory.generatePrivate(keySpec);    
		return privateKey;  
	}

	/**
	 * 获取公钥
	 * 
	 * @param keyMap
	 *            密钥对
	 * @return
	 * @throws Exception
	 */
	public static String getPublicKey(Map<String, Object> keyMap)
			throws Exception {
		Key key = (Key) keyMap.get(PUBLIC_KEY);
		return Base64Utils.encode(key.getEncoded());
	}

	/**
	 * 获取公钥
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static PublicKey getPublicKey(String key) throws Exception {
		byte[] keyBytes;   
		keyBytes = Base64Utils.decode(key);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);  // 公钥使用X509EncodedKeySpec 
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);    
		PublicKey publicKey = keyFactory.generatePublic(keySpec);    
		return publicKey;  
	}
	
	/**
	 * 公钥加密
	 * @param pubkey
	 * @param text
	 * @return
	 */
	public static byte[] encrypt(PublicKey pubkey, String text) {
		try {
			Cipher rsa;
			rsa = Cipher.getInstance(KEY_ALGORITHM);
			rsa.init(Cipher.ENCRYPT_MODE, pubkey);
			return rsa.doFinal(text.getBytes());
		} catch (Exception e) {
			logger.error("encrypt Exception " + e.getMessage(), e);
		}
		return null;
	}

	/**
	 * 私钥解密
	 * @param decryptionKey
	 * @param buffer
	 * @return
	 */
	public static String decrypt(PrivateKey decryptionKey, byte[] buffer) {
		try {
			Cipher rsa;
			rsa = Cipher.getInstance(KEY_ALGORITHM);
			rsa.init(Cipher.DECRYPT_MODE, decryptionKey);
			byte[] utf8 = rsa.doFinal(buffer);
			return new String(utf8, "UTF8");
		} catch (Exception e) {
			logger.error("decrypt Exception " + e.getMessage(), e);
		}
		return null;
	}

	public static KeyPair readKeyPair(File privateKey, String keyPassword)
			throws IOException {
		FileReader fileReader = new FileReader(privateKey);
		PEMReader r = new PEMReader(fileReader, new DefaultPasswordFinder(
				keyPassword.toCharArray()));
		try {
			return (KeyPair) r.readObject();
		} catch (IOException ex) {
			throw ex;
		} finally {
			r.close();
			fileReader.close();
		}
	}

	public static Key readPublicKey(File privateKey, String keyPassword)
			throws IOException {
		FileReader fileReader = new FileReader(privateKey);
		PEMReader r = new PEMReader(fileReader, new DefaultPasswordFinder(
				keyPassword.toCharArray()));
		try {
			return (RSAPublicKey) r.readObject();
		} catch (IOException ex) {
			throw ex;
		} finally {
			r.close();
			fileReader.close();
		}
	}

	public static class DefaultPasswordFinder implements PasswordFinder {
		private final char[] password;
		private DefaultPasswordFinder(char[] password) {
			this.password = password;
		}

		@Override
		public char[] getPassword() {
			return Arrays.copyOf(password, password.length);
		}
	}

	/**
	 * 使用公钥 publickey 加密字符串 key
	 * @param userInfo
	 * @param publickey
	 * @return
	 */
	public static String getEncodeString(String userInfo, File publickey){
		try {
			Security.addProvider(new BouncyCastleProvider());
			
			PublicKey publicKey = RSAUtil.getPublicKey(readFile(publickey));
//			System.out.println("\r加密前文字：\r\n" + userInfo);
			byte[] encodedData = RSAUtil.encryptByPublicKey(userInfo.getBytes(), Base64Utils
					.encode(publicKey.getEncoded()));
			userInfo=Base64Utils.encode(encodedData);
//			System.out.println("加密后文字：\r\n" + userInfo);
		} catch (Exception e) {
			return null;
		}
		return userInfo;
	}
	
	/**
	 * 使用privateKey 解密encodeString
	 * @param encodeString
	 * @param privateKey
	 * @return
	 */
	public static String getDecodeString(String encodeString, File privateKey){
		String target=null;
		try {
			Security.addProvider(new BouncyCastleProvider());
			PrivateKey privatekey = RSAUtil.getPrivateKey(readFile(privateKey));
			byte[] decodedData = RSAUtil.decryptByPrivateKey(
					Base64Utils.decode(encodeString),
					Base64Utils.encode(privatekey.getEncoded()));
			target = new String(decodedData);
//			System.out.println("解密后文: \r\n" + target);
		} catch (Exception e) {
			return null;
		}
		return target;
	}
	
	/**
	 * 读取文件
	 * @param fileName
	 * @return
	 */
	private static String readFile(File fileName) {
		String result = "";
		BufferedReader bufReader = null;
		try {
			InputStreamReader inputReader = new InputStreamReader(new FileInputStream(fileName));
			bufReader = new BufferedReader(inputReader);
			String line = "";
			while ((line = bufReader.readLine()) != null) {
				result += line;
			}
		} catch (Exception e) {
			return null;
		} finally {
			if(bufReader!=null){
				try {
					bufReader.close();
				} catch (IOException e) {
					
				}
			}
		}
		return result;
	}

	/**
	 * 测试
	 */
	public static void test(){
		try {
			File publickey = RSAUtil.getPublicFile();
			File privateKey = RSAUtil.getPrivateFile();
			
			// 使用公钥进行加密
			Security.addProvider(new BouncyCastleProvider());
			String unEncryptStr = "ank@123";
			PublicKey publicKey = RSAUtil.getPublicKey(readFile(publickey));
			System.out.println("加密前unEncryptStr：" + unEncryptStr);
			System.out.println("publicKey: " + Base64Utils.encode(publicKey.getEncoded()));
			byte[] encodedData = RSAUtil.encryptByPublicKey(
					unEncryptStr.getBytes(), 
					Base64Utils.encode(publicKey.getEncoded()));
			String encryptStr = Base64Utils.encode(encodedData);
			System.out.println("加密后encryptStr：" + encryptStr);
			
			// 使用私钥进行解密
			Security.addProvider(new BouncyCastleProvider());
//			KeyPair keyPair = RSAUtil.readKeyPair(privateKey, "");
//			PrivateKey privatekey = keyPair.getPrivate();
			PrivateKey privatekey = RSAUtil.getPrivateKey(readFile(privateKey));
			System.out.println("privatekey：" + Base64Utils.encode(privatekey.getEncoded()));
			byte[] decodedData = RSAUtil.decryptByPrivateKey(
					Base64Utils.decode(encryptStr),
					Base64Utils.encode(privatekey.getEncoded()));
			String decryptStr = new String(decodedData);
			System.out.println("解密后decryptStr：" + decryptStr);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}